package jinyilw.swing;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

public class PixelsTools
{
	private PixelsTools()
	{
	}

	public static int[] getPixelsFromImage(Image img, int[] pixels)
	{
		if (img == null)
			return null;
		int w = img.getWidth(null), h = img.getHeight(null);
		if (w <= 0 || h <= 0)
		{
			img.flush();
			w = img.getWidth(null);
			h = img.getHeight(null);
			if (w <= 0 || h <= 0)
				return null;
		}
		if (pixels == null || pixels.length < w * h)
			pixels = new int[w * h];
		BufferedImage bimg;
		if (img instanceof BufferedImage)
			bimg = (BufferedImage) img;
		else
		{
			bimg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
			Graphics g = bimg.getGraphics();
			g.drawImage(img, 0, 0, null);
			g.dispose();
		}
		return bimg.getRGB(0, 0, w, h, pixels, 0, w);
	}

	public static int[] getPixelsFromImage(Image img)
	{
		return getPixelsFromImage(img, null);
	}

	//	public static BufferedImage getImageFromPixels(int[] pixels, int w, int h,
	//			BufferedImage img)
	//	{
	//		if (pixels == null || w == 0 || h == 0 || pixels.length < w * h)
	//			return null;
	//		if (img != null && img.getWidth() == w && img.getHeight() == h)
	//			img.setRGB(0, 0, w, h, pixels, 0, w);
	//		else
	//		{
	//			int type = img != null
	//					? img.getType()
	//					: BufferedImage.TYPE_INT_ARGB;
	//			img = new BufferedImage(w, h, type);
	//			img.setRGB(0, 0, w, h, pixels, 0, w);
	//		}
	//		return img;
	//	}

	public static void contrastLightFilter(int[] p, float contrast,
			float lightness)
	{
		int a , r , g , b ;
		double redSum = 0, greenSum = 0, blueSum = 0;
		double total = p.length;
		for (int j : p)
		{
			r = (j >> 16) & 0xff;
			g = (j >> 8) & 0xff;
			b = j & 0xff;
			redSum += r;
			greenSum += g;
			blueSum += b;
		}
		int rmeans = (int) (redSum / total);
		int gmeans = (int) (greenSum / total);
		int bmeans = (int) (blueSum / total);
		for (int i = 0; i < total; i++)
		{
			a = (p[i] >> 24) & 0xff;
			r = (p[i] >> 16) & 0xff;
			g = (p[i] >> 8) & 0xff;
			b = p[i] & 0xff;
			r -= rmeans;
			g -= gmeans;
			b -= bmeans;
			r = (int) (r * contrast + rmeans);
			g = (int) (g * contrast + gmeans);
			b = (int) (b * contrast + bmeans);
			p[i] = (a << 24) | (clamp((int) (r * lightness)) << 16)
					| (clamp((int) (g * lightness)) << 8)
					| clamp((int) (b * lightness));
		}
	}

	public static void contrastFilter(int[] p, float contrast)
	{
		int a , r , g , b ;
		double redSum = 0, greenSum = 0, blueSum = 0;
		double total = p.length;
		for (int i = 0; i < total; i++)
		{
			r = (p[i] >> 16) & 0xff;
			g = (p[i] >> 8) & 0xff;
			b = p[i] & 0xff;
			redSum += r;
			greenSum += g;
			blueSum += b;
		}
		int rmeans = (int) (redSum / total);
		int gmeans = (int) (greenSum / total);
		int bmeans = (int) (blueSum / total);
		for (int i = 0; i < total; i++)
		{
			a = (p[i] >> 24) & 0xff;
			r = (p[i] >> 16) & 0xff;
			g = (p[i] >> 8) & 0xff;
			b = p[i] & 0xff;
			r -= rmeans;
			g -= gmeans;
			b -= bmeans;
			r = (int) (r * contrast + rmeans);
			g = (int) (g * contrast + gmeans);
			b = (int) (b * contrast + bmeans);
			p[i] = (a << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b);
		}
	}

	public static void fastBlurFilter(int[] srcPixels, int width, int height,
			int radius)
	{
		int[] dstPixels = new int[width * height];
		blur(srcPixels, dstPixels, width, height, radius);
		blur(dstPixels, srcPixels, height, width, radius);
	}

	static void blur(int[] srcPixels, int[] dstPixels, int width, int height,
			int radius)
	{
		final int windowSize = radius * 2 + 1;
		final int radiusPlusOne = radius + 1;
		int sumAlpha, sumRed, sumGreen, sumBlue, srcIndex = 0, dstIndex, pixel;
		int[] sumLookupTable = new int[256 * windowSize];
		for (int i = 0; i < sumLookupTable.length; i++)
		{
			sumLookupTable[i] = i / windowSize;
		}
		int[] indexLookupTable = new int[radiusPlusOne];
		if (radius < width)
		{
			for (int i = 0; i < indexLookupTable.length; i++)
				indexLookupTable[i] = i;
		} else
		{
			for (int i = 0; i < width; i++)
				indexLookupTable[i] = i;
			for (int i = width; i < indexLookupTable.length; i++)
				indexLookupTable[i] = width - 1;
		}

		for (int y = 0; y < height; y++)
		{
			sumAlpha = sumRed = sumGreen = sumBlue = 0;
			dstIndex = y;
			pixel = srcPixels[srcIndex];
			sumAlpha += radiusPlusOne * ((pixel >> 24) & 0xFF);
			sumRed += radiusPlusOne * ((pixel >> 16) & 0xFF);
			sumGreen += radiusPlusOne * ((pixel >> 8) & 0xFF);
			sumBlue += radiusPlusOne * (pixel & 0xFF);
			for (int i = 1; i <= radius; i++)
			{
				pixel = srcPixels[srcIndex + indexLookupTable[i]];
				sumAlpha += (pixel >> 24) & 0xFF;
				sumRed += (pixel >> 16) & 0xFF;
				sumGreen += (pixel >> 8) & 0xFF;
				sumBlue += pixel & 0xFF;
			}
			for (int x = 0; x < width; x++)
			{
				dstPixels[dstIndex] = sumLookupTable[sumAlpha] << 24
						| sumLookupTable[sumRed] << 16
						| sumLookupTable[sumGreen] << 8
						| sumLookupTable[sumBlue];
				dstIndex += height;
				int nextPixelIndex = x + radiusPlusOne;
				if (nextPixelIndex >= width)
					nextPixelIndex = width - 1;
				int previousPixelIndex = x - radius;
				if (previousPixelIndex < 0)
					previousPixelIndex = 0;
				int nextPixel = srcPixels[srcIndex + nextPixelIndex];
				int previousPixel = srcPixels[srcIndex + previousPixelIndex];
				sumAlpha += (nextPixel >> 24) & 0xFF;
				sumAlpha -= (previousPixel >> 24) & 0xFF;
				sumRed += (nextPixel >> 16) & 0xFF;
				sumRed -= (previousPixel >> 16) & 0xFF;
				sumGreen += (nextPixel >> 8) & 0xFF;
				sumGreen -= (previousPixel >> 8) & 0xFF;
				sumBlue += nextPixel & 0xFF;
				sumBlue -= previousPixel & 0xFF;
			}
			srcIndex += width;
		}
	}

	public static void spotlightFilter(int[] p, int width, int height,
			double factor)
	{
		double contrast = 1.8;
		int index;
		int centerX = width / 2;
		int centerY = height / 2;
		double maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
		int ta, tr , tg , tb ;
		double redSum = 0, greenSum = 0, blueSum = 0;
		double total = p.length;
		for (int i = 0; i < total; i++)
		{
			tr = (p[i] >> 16) & 0xff;
			tg = (p[i] >> 8) & 0xff;
			tb = p[i] & 0xff;
			redSum += tr;
			greenSum += tg;
			blueSum += tb;
		}
		int rmeans = (int) (redSum / total);
		int gmeans = (int) (greenSum / total);
		int bmeans = (int) (blueSum / total);
		for (int row = 0; row < height; row++)
		{
			for (int col = 0; col < width; col++)
			{
				index = row * width + col;
				ta = (p[index] >> 24) & 0xff;
				tr = (p[index] >> 16) & 0xff;
				tg = (p[index] >> 8) & 0xff;
				tb = p[index] & 0xff;
				tr -= rmeans;
				tg -= gmeans;
				tb -= bmeans;
				tr = (int) (tr * contrast + rmeans);
				tg = (int) (tg * contrast + gmeans);
				tb = (int) (tb * contrast + bmeans);
				double scale = (1.0
						- dotDistance(centerX, centerY, col, row) / maxDistance)
						* factor;
				tr = (int) (scale * tr);
				tg = (int) (scale * tg);
				tb = (int) (scale * tb);
				p[index] = (ta << 24) | (clamp(tr) << 16) | (clamp(tg) << 8)
						| clamp(tb);
			}
		}
	}

	public static void HSLFilter(int[] p, int width, int height, double hue,
			double saturation, double lightness)
	{
		double c1o60 = 1.0 / 60.0;
		double c1o255 = 1.0 / 255.0;
		while (hue < 0.0)
		{
			hue += 360;
		}
		while (hue >= 360.0)
		{
			hue -= 360;
		}
		double sat = 127.0d * saturation / 100.0d;
		double lum = 127.0d * lightness / 100.0d;
		double min, max, dif, sum;
		double f1, f2;
		int index = 0;
		double h, s, l;
		double v1, v2, v3, h1;
		for (int row = 0; row < height; row++)
		{
			int ta = 0, tr = 0, tg = 0, tb = 0;
			for (int col = 0; col < width; col++)
			{
				index = row * width + col;
				ta = (p[index] >> 24) & 0xff;
				tr = (p[index] >> 16) & 0xff;
				tg = (p[index] >> 8) & 0xff;
				tb = p[index] & 0xff;
				// convert to HSL space
				min = tr;
				if (tg < min)
					min = tg;
				if (tb < min)
					min = tb;
				max = tr;
				f1 = 0.0;
				f2 = tg - tb;
				if (tg > max)
				{
					max = tg;
					f1 = 120.0;
					f2 = tb - tr;
				}
				if (tb > max)
				{
					max = tb;
					f1 = 240.0;
					f2 = tr - tg;
				}
				dif = max - min;
				sum = max + min;
				l = 0.5 * sum;
				if (dif == 0)
				{
					h = 0.0;
					s = 0.0;
				} else if (l < 127.5)
				{
					s = 255.0 * dif / sum;
				} else
				{
					s = 255.0 * dif / (510.0 - sum);
				}
				h = (f1 + 60.0 * f2 / dif);
				if (h < 0.0)
				{
					h += 360.0;
				}
				if (h >= 360.0)
				{
					h -= 360.0;
				}
				h = h + hue;
				if (h >= 360.0)
				{
					h = h - 360.0;
				}
				s = s + sat;
				if (s < 0.0)
				{
					s = 0.0;
				}
				if (s > 255.0)
				{
					s = 255.0;
				}
				l = l + lum;
				if (l < 0.0)
				{
					l = 0.0;
				}
				if (l > 255.0)
				{
					l = 255.0;
				}
				if (s == 0)
				{
					tr = (int) l;
					tg = (int) l;
					tb = (int) l;
				} else
				{
					if (l < 127.5)
					{
						v2 = c1o255 * l * (255 + s);
					} else
					{
						v2 = l + s - c1o255 * s * l;
					}
					v1 = 2 * l - v2;
					v3 = v2 - v1;
					h1 = h + 120.0;
					if (h1 >= 360.0)
						h1 -= 360.0;
					if (h1 < 60.0)
					{
						tr = (int) (v1 + v3 * h1 * c1o60);
					} else if (h1 < 180.0)
					{
						tr = (int) v2;
					} else if (h1 < 240.0)
					{
						tr = (int) (v1 + v3 * (4 - h1 * c1o60));
					} else
					{
						tr = (int) v1;
					}
					h1 = h;
					if (h1 < 60.0)
					{
						tg = (int) (v1 + v3 * h1 * c1o60);
					} else if (h1 < 180.0)
					{
						tg = (int) v2;
					} else if (h1 < 240.0)
					{
						tg = (int) (v1 + v3 * (4 - h1 * c1o60));
					} else
					{
						tg = (int) v1;
					}
					h1 = h - 120.0;
					if (h1 < 0.0)
					{
						h1 += 360.0;
					}
					if (h1 < 60.0)
					{
						tb = (int) (v1 + v3 * h1 * c1o60);
					} else if (h1 < 180.0)
					{
						tb = (int) v2;
					} else if (h1 < 240.0)
					{
						tb = (int) (v1 + v3 * (4 - h1 * c1o60));
					} else
					{
						tb = (int) v1;
					}
				}
				p[index] = (ta << 24) | (tr << 16) | (tg << 8) | tb;
			}
		}
	}

	public static void blockFilter(int[] p, int width, int height)
	{
		for (int y = 0; y < height; y += width)
		{
			for (int x = 0; x < width; x += width)
			{
				int h = Math.min(width, height - y);
				int t = width * h;
				int r = 0, g = 0, b = 0;
				int argb;
				int i = 0;
				for (int by = 0; by < h; by++)
				{
					for (int bx = 0; bx < width; bx++)
					{
						argb = p[i];
						r += (argb >> 16) & 0xff;
						g += (argb >> 8) & 0xff;
						b += argb & 0xff;
						i++;
					}
				}
				argb = ((r / t) << 16) | ((g / t) << 8) | (b / t);
				i = 0;
				for (int by = 0; by < h; by++)
				{
					for (int bx = 0; bx < width; bx++)
					{
						p[i] = (p[i] & 0xff000000) | argb;
						i++;
					}
				}
			}
		}
	}

	public static void lightnessFilter(int[] p, float lightness)
	{
		if (p == null)
			return;
		for (int i = 0; i < p.length; i++)
		{
			int a = (p[i] >> 24) & 0xff, r = (p[i] >> 16) & 0xff,
					g = (p[i] >> 8) & 0xff, b = p[i] & 0xff;
			p[i] = (a << 24) | (clamp((int) (r * lightness)) << 16)
					| (clamp((int) (g * lightness)) << 8)
					| clamp((int) (b * lightness));
		}
	}

	public static void alphaFilter(int[] p, int alpha)
	{
		if (p == null)
			return;
		if (alpha >= 255 || alpha <= 0)
			return;
		for (int i = 0; i < p.length; i++)
		{
			int r = (p[i] >> 16) & 0xff, g = (p[i] >> 8) & 0xff,
					b = p[i] & 0xff;
			p[i] = (alpha << 24) | (r << 16) | (g << 8) | b;
		}
	}

	public static void averageBrightFilter(int[] p, int average, int bright)
	{
		if (p == null)
			return;
		for (int i = 0; i < p.length; i++)
		{
			int a = (p[i] >> 24) & 0xff, r = (p[i] >> 16) & 0xff,
					g = (p[i] >> 8) & 0xff, b = p[i] & 0xff;
			int gray = Math.min(255, (int) (r * 0.28 + g * 0.59 + b * 0.13));
			if (gray == 0)
			{
				int ab = average / 2 + bright;
				p[i] = (a << 24) | (ab << 16) | (ab << 8) | ab;
			} else
			{
				double lightness = gray < average
						? (average / (double) gray)
						: 1;
				p[i] = (a << 24) | (clamp((int) (r * lightness + bright)) << 16)
						| (clamp((int) (g * lightness + bright)) << 8)
						| clamp((int) (b * lightness + bright));
			}
		}
	}

	public static double dotDistance(int centerX, int centerY, int px, int py)
	{
		double xx = centerX - px;
		double yy = centerY - py;
		return Math.sqrt(xx * xx + yy * yy);
	}

	public static double colorDistance(int rgb1, int rgb2)
	{
		int r1 = (rgb1 >> 16) & 0xff, g1 = (rgb1 >> 8) & 0xff, b1 = rgb1 & 0xff;
		int r2 = (rgb2 >> 16) & 0xff, g2 = (rgb2 >> 8) & 0xff, b2 = rgb2 & 0xff;
		double rr = r1 - r2;
		double gg = g1 - g2;
		double bb = b1 - b2;
		return Math.sqrt(rr * rr + gg * gg + bb * bb);
	}

	public static double colorDistance(Color c1, Color c2)
	{
		int r1 = c1.getRed(), g1 = c1.getGreen(), b1 = c1.getBlue();
		int r2 = c2.getRed(), g2 = c2.getGreen(), b2 = c2.getBlue();
		double rr = r1 - r2;
		double gg = g1 - g2;
		double bb = b1 - b2;
		return Math.sqrt(rr * rr + gg * gg + bb * bb);
	}

	public static int clamp(int value)
	{
		return value > 255 ? 255 : (Math.max(value, 0));
	}

	public static int[] getAreaPixels(int[] mainPixels, Rectangle mainArea,
			int[] pixels, Rectangle area)
	{
		int mx = mainArea.x, my = mainArea.y, mw = mainArea.width,
				mh = mainArea.height;
		int x = area.x, y = area.y, w = area.width, h = area.height;
		if (pixels == null)
			pixels = new int[w * h];
		int x0 = (Math.max(x, mx));
		int y0 = (Math.max(y, my));
		int ww = (x + w > mx + mw ? mx + mw - x0 : x + w - x0);
		int hh = (y + h > my + mh ? my + mh - y0 : y + h - y0);
		if (ww > 0 && hh > 0)
		{
			for (int r = 0; r < hh; r++)
			{
				int pr = y0 - y + r, mr = y0 - my + r;
				for (int c = 0; c < ww; c++)
				{
					int pc = x0 - x + c, mc = x0 - mx + c;
					pixels[pr * w + pc] = mainPixels[mr * mw + mc];
				}
			}
		}
		return pixels;
	}

	public static int getGrayValue(int[] p)
	{
		if (p == null)
			return 0;
		int sum = 0;
		for (int j : p)
		{
			int r = (j >> 16) & 0xff, g = (j >> 8) & 0xff,
					b = j & 0xff;
			sum += Math.min(255, ((r * 299) + (g * 587) + (b * 114)) / 1000);
		}
		return (sum / p.length);
	}

	public static int getGrayValue(Image img)
	{
		return getGrayValue(getPixelsFromImage(img));
	}
}
